/**
 * Copyright (c) 2020 - present, Inspur Genersoft Co., Ltd.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *       http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
import { computed, defineComponent, ref, SetupContext, watch } from "vue";
import { DatePickerCalendarProps, datePickerCalendarProps } from "./calendar.props";
import { DateObject, MonthTag, Period, SelectMode } from "../../types/common";
import { CalendarWeekItem, CalenderDayItem } from "../../types/calendar";

import './calendar.css';
import { KeyCode, useEvent } from "../../composition/use-event";
import { useCompare } from "../../composition/use-compare";

export default defineComponent({
    name: 'FDatePickerCalendarView',
    props: datePickerCalendarProps,
    emits: ['click', 'clickWeek', 'keyDown', 'mouseEnter', 'mouseLeave'] as (string[] & ThisType<void>) | undefined,
    setup(props: DatePickerCalendarProps, context: SetupContext) {
        const dates = ref<CalendarWeekItem[]>(props.dates);
        const daysInWeek = ref<string[]>(props.daysInWeek);
        const enableKeyboadNavigate = ref(props.enableKeyboadNavigate);
        const enableMarkCurrent = ref(props.enableMarkCurrent);
        const enablePeriod = ref(props.enablePeriod);
        const firstDayOfTheWeek = ref(props.firstDayOfTheWeek);
        const selected = ref<DateObject | null>(props.selected);
        const selectedPeriod = ref<Period | null>(props.selectedPeriod);
        const selectedWeek = ref<CalendarWeekItem | null>(props.selectedWeek);
        const selectMode = ref<SelectMode>(props.selectMode as SelectMode);
        const showWeekNumber = ref(props.showWeekNumber);
        const weekTitle = ref(props.weekTitle);

        const shouldShowWeekTitle = computed(() => {
            return showWeekNumber.value && firstDayOfTheWeek.value === 'Mon';
        });

        watch(() => props.dates, () => {
            dates.value = props.dates;
        });
        watch(() => props.selected, () => {
            selected.value = props.selected;
        });

        const { equal, inPeriod, isInitializedDate, equalOrEarlier, isPoint } = useCompare();
        const { getKeyCodeFromEvent } = useEvent();

        const weekClass = (currentWeek: CalendarWeekItem) => {
            const isSelected = !!selectedWeek.value
                && currentWeek.numberInTheYear === selectedWeek.value.numberInTheYear
                && currentWeek.year === selectedWeek.value.year;
            const classObject = {
                'f-datepicker-selectWeek': selectMode.value === 'week',
                'f-datepicker-selectedWeek': selectMode.value === 'week' && isSelected
            } as Record<string, boolean>;
            return classObject;
        };

        const shouldShowWeekNumber = computed(() => {
            return showWeekNumber.value && firstDayOfTheWeek.value === 'Mon';
        });

        function isDateInPeriod(day: DateObject) {
            return inPeriod(day, selectedPeriod.value);
        }

        function isDateRangeBeginOrEndSame(day: DateObject) {
            return !!selectedPeriod.value && isPoint(selectedPeriod.value, day);
        }

        function isDateSame(day: DateObject) {
            return !!selected.value && equal({ year: selected.value.year, month: selected.value.month, day: selected.value.day }, day);
        }

        const dayContainerClass = (currentDay: CalenderDayItem, weekIndex: number, dayIndex: number) => {
            const showDateRange = selectMode.value !== 'week' && currentDay.tag === MonthTag.current &&
                (
                    (
                        enablePeriod.value && isDateInPeriod(currentDay.date) && !isDateRangeBeginOrEndSame(currentDay.date)
                    ) || currentDay.range
                );
            const notInCurrentMonth = currentDay.tag === MonthTag.previous || currentDay.tag === MonthTag.next;
            const classObject = {
                'f-datepicker-range': showDateRange,
                'f-datepicker-no-currmonth': notInCurrentMonth,
                'f-datepicker-disabled': currentDay.disable,
                'f-datepicker-singledate': !currentDay.disable
            } as Record<string, boolean>;
            const className = `d_${weekIndex}_${dayIndex}`;
            classObject[className] = true;
            return classObject;
        };

        const dayClass = (currentDay: CalenderDayItem) => {
            const isSelected = selectMode.value !== 'week' && currentDay.tag === MonthTag.current &&
                ((!enablePeriod.value &&
                    isDateSame(currentDay.date)) ||
                    (enablePeriod.value &&
                        isDateRangeBeginOrEndSame(currentDay.date)));

            const shouldMarkCurrentDay = currentDay.isCurrent && enableMarkCurrent.value;
            const shouldHight = currentDay.highlight &&
                (currentDay.tag === MonthTag.previous ||
                    currentDay.tag === MonthTag.next ||
                    currentDay.disable);

            const classObject = {
                'f-datepicker-date': true,
                'f-datepicker-selected': isSelected,
                'f-datepicker-current': shouldMarkCurrentDay,
                'f-datepicker-opacity': shouldHight,
                'f-datepicker-highlight': currentDay.highlight,
                'f-datepicker-disabled': currentDay.disable
            } as Record<string, boolean>;
            return classObject;
        };

        function onClick($event: Event, target: CalenderDayItem) {
            if (target.disable || selectMode.value === 'week') {
                return;
            }
            $event.stopPropagation();
            context.emit('click', target.date);
        }

        function onClickWeek($event: Event, target: CalendarWeekItem) {
            if (selectMode.value !== 'week') {
                return;
            }
            $event.stopPropagation();
            context.emit('clickWeek', target);
        }

        function onKeyDown($event: KeyboardEvent, target: CalenderDayItem) {
            const keyCode: number = getKeyCodeFromEvent($event);
            if (keyCode !== KeyCode.tab) {
                $event.preventDefault();

                if (keyCode === KeyCode.enter || keyCode === KeyCode.space) {
                    onClick($event, target);
                } else if (enableKeyboadNavigate.value) {
                    context.emit('keyDown', target.date);
                }
            }
        }

        function onMouseEnter(target: CalenderDayItem) {
            if (selectedPeriod.value &&
                isInitializedDate(selectedPeriod.value.from) &&
                !isInitializedDate(selectedPeriod.value.to)
            ) {
                dates.value.forEach((week: CalendarWeekItem) => {
                    week.days.forEach((item: CalenderDayItem) => {
                        item.range = !!selectedPeriod.value && (
                            (equalOrEarlier(selectedPeriod.value.from, item.date) && equalOrEarlier(item.date, target.date)) ||
                            (equalOrEarlier(item.date, selectedPeriod.value.from) && equalOrEarlier(target.date, item.date))
                        );
                    });
                });
                context.emit('mouseEnter', target.date);
            }
        }

        function onMouseLeave() {
            dates.value.forEach((week: CalendarWeekItem) => {
                week.days.forEach((item: CalenderDayItem) => {
                    item.range = false;
                });
            });
            context.emit('mouseLeave');
        }

        return () => {
            return (
                <>
                    <div class="f-datepicker-table-wrapper">
                        <table class="f-datepicker-table" cellpadding="0">
                            <thead>
                                <tr>
                                    {shouldShowWeekTitle.value &&
                                        <th class="f-datepicker-weeknbr-title">
                                            {weekTitle.value}
                                        </th>
                                    }
                                    {
                                        daysInWeek.value && daysInWeek.value.map((day: string) => {
                                            return (
                                                <th scope="col" style="padding-bottom: 8px;padding-top: 4px">
                                                    {day}
                                                </th>
                                            );
                                        })
                                    }
                                </tr>
                            </thead>
                            <tbody>
                                {
                                    dates.value &&
                                    dates.value.map((week: CalendarWeekItem, weekIndex: number) => {
                                        return (
                                            <tr class={weekClass(week)} onClick={(payload: MouseEvent) => onClickWeek(payload, week)}>
                                                {
                                                    shouldShowWeekNumber.value && (
                                                        <td class="f-datepicker-weeknbr">
                                                            <div class="f-datepicker-date">{week.numberInTheYear}</div>
                                                        </td>
                                                    )
                                                }
                                                {
                                                    week.days && week.days.map((item: CalenderDayItem, dayIndex: number) => {
                                                        return (
                                                            <td id={`d_${weekIndex}_${dayIndex}`} tabindex="0"
                                                                class={dayContainerClass(item, weekIndex, dayIndex)}
                                                                onClick={(payload: MouseEvent) => onClick(payload, item)}
                                                                onKeydown={(payload: KeyboardEvent) => onKeyDown(payload, item)}
                                                                onMouseenter={() => onMouseEnter(item)}
                                                                onMouseleave={() => onMouseLeave()}>
                                                                {
                                                                    item.marked && item.marked.marked && (
                                                                        <span class="f-datepicker-markdate"
                                                                            style={{ 'background-color': item.marked.color }}>
                                                                        </span>
                                                                    )
                                                                }
                                                                <div class={dayClass(item)} >
                                                                    {item.date.day}
                                                                </div>
                                                            </td >
                                                        );
                                                    })
                                                }
                                            </tr>
                                        );
                                    })
                                }
                            </tbody >
                        </table >
                    </div>
                </>
            );
        };
    }
});
